/*
 *  Splitsbrowser - SplitsGraph
 *
 *  Original Copyright (c) 2000  Dave Ryder
 *  Version 2 Copyright (c) 2002 Reinhard Balling
 *   Version 2.1 Copyright (c) 2002 Reinhard Balling
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 */
/*
 * Version control info - Do not edit
 * Created:    Dave Ryder
 * Version:    $Revision: 1.4 $
 * Changed:    $Date: 2003/09/18 19:29:40 $
 * Changed by: $Author: daveryder $
 */
package org.uk.splitsbrowser.client.view;

import java.util.Vector;

import org.uk.splitsbrowser.shared.results.Course;
import org.uk.splitsbrowser.shared.results.Result;
import org.uk.splitsbrowser.shared.results.SelectedResults;
import org.uk.splitsbrowser.shared.results.Time;
import org.uk.splitsbrowser.shared.util.ICompare;
import org.uk.splitsbrowser.shared.util.Message;
import org.uk.splitsbrowser.shared.util.Sorter;

import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.GestureStartEvent;
import com.google.gwt.event.dom.client.GestureStartHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchEndHandler;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchMoveHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

/**
 * A panel which displays a five diagrams of orienteering results
 * <ul>
 * <li>the difference to the reference time</li>
 * <li>the absolute time after each control (absolute time = starttime +
 * elapsedTime)</li>
 * <li>total position after each control</li>
 * <li>leg position for each leg</li>
 * <li>percent time behind reference runner</li>
 * </ul>
 * The default reference runner is the winner, but any runner with a valid run
 * can be chosen as reference runner. It is also possible to choose the
 * "ideal time", that is the sum of best split times as the reference time.<br>
 * <br>
 * 
 * This class is in urgent need of refactoring. It started as a single view (.)
 * which has since grown in a rather non-oop way (just look for the various
 * switch statements).<br>
 * Also the functionality of pop-ups needs to be moved out to a class of its
 * own.<br>
 * 
 * @version $Revision: 1.4 $
 */
public class SplitsGraph extends Composite {

	private static SplitsGraphUiBinder uiBinder = GWT.create(SplitsGraphUiBinder.class);

	interface SplitsGraphUiBinder extends UiBinder<Widget, SplitsGraph> {
	}

	public enum GraphType {
		COMPARISON, ABSOLUTE, PERCENTBEHIND, POS, SPLITPOS
	};

	/* Event data */
	private Course course = null;
	private Result reference;
	private SelectedResults selectedResults;
	private String eventName;
	private int selectedSplit = -2;
	int referencePercentage;

	/** Collection of results selected by the use to be displayed */
	private static Vector<Integer> displayedResults = new Vector<Integer>();

	/**
	 * Collection of colours used to draw the graph lines of size
	 * displayedResults
	 */
	private static Vector<CssColor> displayColors = new Vector<CssColor>();

	/* Display configuration */
	private boolean showSplits = true;
	private boolean showTimeBehind = false;
	private boolean showTimeLoss = true;
	private boolean showTotalTime = false;
	private boolean thickLine = true;
	private GraphType graphType = GraphType.COMPARISON;
	
	private CssColor color1 = CssColor.make(216, 220, 255); // First graph stripe
	private CssColor color2 = CssColor.make(224, 232, 240); // Second graph stripe
	private CssColor gridColor = CssColor.make("white"); // Grid colour
	private CssColor textColor = CssColor.make("black"); // text colour  

	/* Internal variables */
	Canvas canvas;
	Canvas frontCanvas;
	Context2d context;
	Context2d frontContext;

	private Axis xAxis = new Axis(false);
	private Axis yAxis = new Axis(false);
	private int LEFT_AXIS = 0;
	private int RIGHT_AXIS = 1;
	
	private int legendSlotHeight;

	private int[] xPos = null; // Times where the splits are rendered

	private final int BOTTOM_MARGIN = 30;
	private final int LEFT_MARGIN = 20;
	private final int RIGHT_MARGIN = 10;
	private final int TOP_MARGIN = 25;
	private final int FONT_HEIGHT = 10;

	private boolean computeDimensions = true; // Do dimensions need recomputing
	private double controlWidth;   
	private double controlHeight;
	
    private LessThanResult lt = new LessThanResult();


	/** Utility function to draw a line */
	private static void drawLine(Context2d context, double x, double y, double x2,
			double y2) {
		context.beginPath();
		context.moveTo(x, y);
		context.lineTo(x2, y2);
		context.stroke();

	}

	/** Constructor for the SplitsGraph object */
	public SplitsGraph() {
		initWidget(uiBinder.createAndBindUi(this));

		this.canvas = Canvas.createIfSupported();
		this.frontCanvas = Canvas.createIfSupported();
		this.context = canvas.getContext2d();
		this.frontContext = frontCanvas.getContext2d();
		
		initHandlers();

		this.context.setFont("italic 10pt SansSerif");
	}

	/**
	 * Sets the color of the first control strip in the splits graph
	 * 
	 * @param newColor1
	 *            The new color1 value
	 */
	public void setColor1(CssColor newColor1) {
		if (color1 != newColor1) {
			color1 = newColor1;
			paint();
		}
	}

	/**
	 * Gets the first color used in the graph stripes
	 * 
	 * @return The color1 value
	 */
	public CssColor getColor1() {
		return color1;
	}

	/**
	 * Sets the color of the second contol strip in the splits graph
	 * 
	 * @param newColor2
	 *            The new color2 value
	 */
	public void setColor2(CssColor newColor2) {
		if (color2 != newColor2) {
			color2 = newColor2;
			paint();
		}
	}

	/**
	 * Gets the second color used in the graph stripes
	 * 
	 * @return The color2 value
	 */
	public CssColor getColor2() {
		return color2;
	}

	/**
	 * Sets the course attribute of the SplitsGraph object
	 * 
	 * @param newCourse
	 *            The new course value
	 */
	public void setCourse(Course newCourse) {
		course = newCourse;

		displayedResults.removeAllElements();
		displayColors.removeAllElements();
		reference = selectedResults.getReferenceResult();

		/*
		 * Create an array to hold the X positions
		 */
		if (newCourse != null) {
			xPos = new int[course.getNumControls() + 3];
			// Add 2 extra spaces: One for start, one for finish, one for infinity
		}
	}

	/**
	 * Returns if a given result is displayed
	 */
	public boolean isDisplayed(int resultIndex) {
		int index = displayedResults.indexOf(new Integer(resultIndex));

		return (index != -1);
	}

	public void setEventName(String newEventName) {
		eventName = newEventName;
	}

	/**
	 * Sets the graphType attribute of the SplitsGraph object
	 * 
	 * @param newGraphType
	 *            The new graphType value
	 * @exception Exception
	 *                Description of the Exception
	 */
	public void setGraphType(GraphType newGraphType) throws Exception {

		if (graphType != newGraphType) {
			graphType = newGraphType;
			invalidateDimensions();
		}
	}

	/**
	 * Sets the gridColor attribute of the SplitsGraph object
	 * 
	 * @param newColor
	 *            The new gridColor value
	 */
	public void setGridColor(CssColor newColor) {
		gridColor = newColor;
		paint();
	}

	/**
	 * Gets the gridColor attribute of the SplitsGraph object
	 * 
	 * @return The gridColor value
	 */
	public CssColor getGridColor() {
		return gridColor;
	}

	public void setSelectedResults(SelectedResults newSelectedResults) {
		selectedResults = newSelectedResults;
	}

	/**
	 * Sets the showSplits attribute of the SplitsGraph object
	 * 
	 * @param newShowSplits
	 *            The new showSplits value
	 */
	public void setShowSplits(boolean newShowSplits) {
		showSplits = newShowSplits;
		invalidateDimensions();
	}

	/**
	 * Gets the showSplits attribute of the SplitsGraph object
	 * 
	 * @return The showSplits value
	 */
	public boolean getShowSplits() {
		return showSplits;
	}

	/**
	 * Sets the showTimeBehind attribute of the SplitsGraph object
	 * 
	 * @param newShowTimeBehind
	 *            The new showTimeBehind value
	 */
	public void setShowTimeBehind(boolean newShowTimeBehind) {
		showTimeBehind = newShowTimeBehind;
		invalidateDimensions();
	}

	/**
	 * Gets the showTimeBehind attribute of the SplitsGraph object
	 * 
	 * @return The showTimeBehind value
	 */
	public boolean getShowTimeBehind() {
		return showTimeBehind;
	}

	/**
	 * Sets the showTimeLoss attribute of the SplitsGraph object
	 * 
	 * @param newshowTimeLoss
	 *            The new showTimeLoss value
	 */
	public void setShowTimeLoss(boolean newshowTimeLoss) {
		showTimeLoss = newshowTimeLoss;
		invalidateDimensions();
	}

	/**
	 * Gets the showTimeLoss attribute of the SplitsGraph object
	 * 
	 * @return The showTimeLoss value
	 */
	public boolean getShowTimeLoss() {
		return showTimeLoss;
	}

	/**
	 * Sets the showTotalTime attribute of the SplitsGraph object
	 * 
	 * @param newShowTotalTime
	 *            The new showTotalTime value
	 */
	public void setShowTotalTime(boolean newShowTotalTime) {
		showTotalTime = newShowTotalTime;
		invalidateDimensions();
	}

	/**
	 * Gets the showTotalTime attribute of the SplitsGraph object
	 * 
	 * @return The showTotalTime value
	 */
	public boolean getShowTotalTime() {
		return showTotalTime;
	}

	/**
	 * Sets the thickLine attribute of the SplitsGraph object
	 * 
	 * @param newThickLine
	 *            The new thickLine value
	 */
	public void setThickLine(boolean newThickLine) {
		thickLine = newThickLine;
		paint();
	}

	/**
	 * Displays a given result index.
	 * 
	 * @param newResult
	 *            is an index into selectedResults.results
	 */
	public void displayResult(int newResult) {
		/*
		 * Insert the result into the vector based on total time
		 */
		CssColor[] colors = { CssColor.make("red"), CssColor.make("blue"),
				CssColor.make("green"), CssColor.make("black"),
				CssColor.make("0xCC0066"), CssColor.make("0x000099"),
				CssColor.make("0xFFCC00"), CssColor.make("0x996600"),
				CssColor.make("0x9900FF"), CssColor.make("0xCCCC00"),
				CssColor.make("0xFFFF66"), CssColor.make("0xCC6699"),
				CssColor.make("0x99FF33"), CssColor.make("0x3399FF"),
				CssColor.make("0xCC33CC"), CssColor.make("0x33FFFF"),
				CssColor.make("0xFF00FF") };

		if (!isDisplayed(newResult)) {
			displayedResults.addElement(new Integer(newResult));
			displayColors.addElement(colors[newResult % colors.length]);
			invalidateDimensions();
		}
	}

	/**
	 * Invalidate the dimensions of the graph <li>Recalculate the physical
	 * limits</li> <li>Recalculate the x-positions for each control</li> Repaint
	 * the graph
	 */
	public void invalidateDimensions() {
		// System.out.println("invalidateDimensions");
		computeDimensions = true;
		paint();
	}
	
	/**
	 * Description of the Method
	 * 
	 * @param g
	 *            Description of the Parameter
	 */
	public void paint() 	{
		
		Context2d g = this.context;

		int newWidth = g.getCanvas().getWidth();
		int newHeight = g.getCanvas().getHeight();

		reference = selectedResults.getReferenceResult();

		// Just paint the background if no course data has been set.
		if (course == null) {
			g.setFillStyle(g.getCanvas().getStyle().getBackgroundColor());
			g.fillRect(0, 0, this.controlWidth, this.controlHeight);

			return;
		}

		// Compute dimensions if required
		if (computeDimensions || (this.controlWidth != newWidth)
				|| (this.controlHeight != newHeight)) {
			computeGraphDimensions(g);
			computeXPositions();
		}

		drawBackground(g);
		
		drawResults(g);

	}

	/** Removes all the displayed results */
	public void removeAllResults() {
		displayedResults.removeAllElements();
		displayColors.removeAllElements();
		invalidateDimensions();
	}

	
	public void removeResult(int iResult) {
		/*
		 * Hide the result and repaint the screen
		 */
		int index = (int) displayedResults.indexOf(new Integer(iResult));

		if (index != -1) {
			displayedResults.removeElementAt(index);
			displayColors.removeElementAt(index);
			invalidateDimensions();
		}
	}

	protected int getAxisWidth(Context2d g, int axis) {
		int MAX_TEXT_WIDTH = 300;
		int MIN_TEXT_WIDTH = 20;

		int maxTextWidth = 0;

		if (selectedResults.getNumResults() == 0) {
			return 0;
		}

		for (int i = 0; i < displayedResults.size(); i++) {
			Integer resultIndex = (Integer) displayedResults.elementAt(i);

			Result result = selectedResults.getResult(resultIndex.intValue());
			int w;

			if (axis == LEFT_AXIS) {
				w = (int) g.measureText(getLeftText(result)).getWidth();
			} else {
				w = (int) (g.measureText(
						getRightText(result, result.getNumControls()))
						.getWidth() + 20);
			}

			maxTextWidth = Math.max(maxTextWidth, w);
		}

		return (Math.max(MIN_TEXT_WIDTH,
				(Math.min(maxTextWidth, MAX_TEXT_WIDTH))));
	}

	protected String getLeftText(Result result) {
		if (graphType == GraphType.ABSOLUTE) {
			if ((result.getStartTime() == null)
					|| !result.getStartTime().isValid()) {
				return Message.get("Graph.NoStartTime") + "  ";
			} else {
				String pfx = (result.isValid() ? " " : "*");

				return (result.getStartTime().toString() + " " + pfx
						+ result.getName() + "  ");
			}
		} else {
			return ("");
		}
	}

	protected String getRightText(Result result, int iSplit) {
		// Mark invalid results with a *
		String pfx = (result.isValid() ? "" : "*");
		StringBuffer s = new StringBuffer(pfx + result.getName());

		if (iSplit > -1) {
			if (showTotalTime) {
				s.append("  " + result.getTime(iSplit).toString());

				int pos = result.getTotalPos(iSplit);

				if (pos > 0) {
					s.append(" (" + new Integer(pos).toString() + ")");
				} else {
					s.append(" (*)");
				}
			}

			if (showSplits) {
				s.append("   " + result.getSplitTime(iSplit).toString());

				int pos = result.getSplitPos(iSplit);

				if (result.getSplitTime(iSplit).isValid()) {
					s.append(" (" + new Integer(pos).toString() + ")");
				} else {
					s.append(" (*)");
				}
			}

			if (showTimeBehind) {
				Result fastest = result.getCourse().getFastestLegTime(iSplit);
				s.append("  "
						+ result.getSplitTime(iSplit)
								.subtract(fastest.getSplitTime(iSplit))
								.toString());

				if (graphType == GraphType.PERCENTBEHIND) {
					Time timeY = getYTime(result, iSplit);

					if (timeY.isValid()) {
						s.append(" (" + (timeY.asSeconds() / 60) + "%)");
					}
				}
			}

			if (showTimeLoss) {
				s.append("  " + result.getTimeLoss(iSplit).toString());
			}
		}

		return (s.toString());
	}

	protected Time getYFirst(Result result) {
		switch (graphType) {
		case COMPARISON:
			return Time.ZEROTIME;

		case ABSOLUTE:
			return result.getStartTime();

		case PERCENTBEHIND:
			return Time.ZEROTIME;

		case POS:
			return Time.NULLTIME;

		case SPLITPOS:
			return Time.NULLTIME;
		}

		return Time.ZEROTIME;
	}

	protected Time getYTime(Result result, int split) {
		switch (graphType) {

		case COMPARISON:
			return result.getTime(split).subtract(reference.getTime(split));

		case ABSOLUTE:
			return result.getAbsoluteTime(split).subtract(
					reference.getTime(split));

		case PERCENTBEHIND:

			Time timeR = result.getSplitTime(split);
			Time timeRef = reference.getSplitTime(split);

			if (timeR.isValid() && timeRef.isValid()
					&& (timeRef.asSeconds() > 0)) {
				return new Time(
						((timeR.asSeconds() * 6000) / timeRef.asSeconds()) - 6000,
						true);
			} else {
				return Time.INVALIDTIME;
			}

		case POS:
			return new Time(result.getTotalPos(split) * 60,
					(result.getTotalPos(split) > 0) ? true : false);

			// Y-Axis expects a time
		case SPLITPOS:
			return new Time(result.getSplitPos(split) * 60, result
					.getSplitTime(split).isValid());
		}

		return Time.INVALIDTIME;
	}

	protected void computeGraphDimensions(Context2d g) {

		int width = g.getCanvas().getWidth();
		int height = g.getCanvas().getHeight();

		// set the physical graph dimensions
		xAxis.setMinPixel(LEFT_MARGIN + getAxisWidth(g, LEFT_AXIS));
		xAxis.setMaxPixel(width - RIGHT_MARGIN - getAxisWidth(g, RIGHT_AXIS));
		yAxis.setMinPixel(TOP_MARGIN);
		yAxis.setMaxPixel(height - BOTTOM_MARGIN);

		/*
		 * Compute the logical (time) limits
		 */
		/*
		 * Y axis limits
		 */
		int maxTime;
		int minTime;

		if (displayedResults.size() == 0) {
			minTime = 0;
			maxTime = 1;
		} else {
			maxTime = 0;
			minTime = Time.MAXTIME.asSeconds();

			for (int i = 0; i < displayedResults.size(); i++) {
				Result result = selectedResults
						.getResult(((Integer) displayedResults.elementAt(i))
								.intValue());
				Time time;

				// Handle first (start) time
				time = getYFirst(result);

				if (time.isValid()) {
					maxTime = Math.max(maxTime, time.asSeconds());
					minTime = Math.min(minTime, time.asSeconds());
				}

				// ...and each of the splits
				for (int splitCount = 0; splitCount <= course.getNumControls(); splitCount++) {
					time = getYTime(result, splitCount);

					if (time.isValid()) {
						maxTime = Math.max(maxTime, time.asSeconds());
						minTime = Math.min(minTime, time.asSeconds());
					}
				}
			}
		}

		if (minTime == Time.MAXTIME.asSeconds()) {
			minTime = maxTime - 1;
		}

		// System.out.println("["+minTime+","+maxTime+"]");
		yAxis.setMaxTime(new Time(maxTime));
		yAxis.setMinTime(new Time(minTime));

		/*
		 * X axis limits
		 */
		xAxis.setMinTime(Time.ZEROTIME);
		xAxis.setMaxTime(reference.getTime(reference.getNumControls()));

		computeDimensions = false;
	}

	protected void computeXPositions() {
		/*
		 * Compute the X positions for the controls 0=Start, 1..numControls(),
		 * numControls()+1=Finish
		 */
		xPos[0] = xAxis.toPixel(0);

		for (int i = 0; i <= course.getNumControls(); i++) {
			// System.out.println("[paint] i="+i+",Time="+reference.getTime(i).asSeconds());
			xPos[i + 1] = xAxis.toPixel(reference.getTime(i).asSeconds());
		}

		xPos[course.getNumControls() + 2] = Integer.MAX_VALUE >> 1;
	}

	protected void drawBackground(Context2d g) {
		/*
		 * First paint the Frame around the drawing area
		 */

		controlWidth = g.getCanvas().getWidth();
		controlHeight = g.getCanvas().getHeight();

		g.setFillStyle(g.getCanvas().getStyle().getBackgroundColor());
		// TODO was drawrect3d
	//	g.drawrect3d(1, 1, controlWidth - 2, controlHeight - 2, false);
		g.fillRect(1, 1, controlWidth - 2, controlHeight - 2);

		int halfFontHeight = FONT_HEIGHT / 2;

		/*
		 * Draw coloured stripes for each control
		 */
		boolean useColor1 = true;

		for (int i = 1; i <= (course.getNumControls() + 1); i++) {
			if (useColor1) {
				g.setFillStyle(color1.value());
			} else {
				g.setFillStyle(color2.value());
			}

			g.fillRect(xPos[i - 1], yAxis.getMinPixel(), xPos[i] - xPos[i - 1],
					yAxis.getMaxPixel() - yAxis.getMinPixel());
			useColor1 = !useColor1;
		}

		g.setStrokeStyle(gridColor.value());

		/*
		 * Draw a horizontal grid
		 */
		int x0 = xAxis.getMinPixel();
		int x1 = xAxis.getMaxPixel();

		for (int i = 0; i < yAxis.getNumLabels(); i++) {
			drawLine(g, x0, yAxis.getLabelPixel(i), x1, yAxis.getLabelPixel(i));
		}

		/*
		 * Draw a box around the graph
		 */
		g.setStrokeStyle(gridColor.value());
		g.rect(xAxis.getMinPixel() - 1, 
			   yAxis.getMinPixel(),
			   xAxis.getMaxPixel() - xAxis.getMinPixel() + 1, 
			   yAxis.getMaxPixel() - yAxis.getMinPixel() );
		/*
		 * TODO g.draw3DRect(xAxis.getMinPixel() - 1, yAxis.getMinPixel(),
		 * xAxis.getMaxPixel() - xAxis.getMinPixel() + 1, yAxis.getMaxPixel() -
		 * yAxis.getMinPixel(), false);
		 */

		// Draw the axis labels
		g.setStrokeStyle(this.textColor);

		// Control numbers at the top
		int y = yAxis.getMinPixel() - 5;

		for (int i = 0; i < (course.getNumControls() - 1); i++) {
			String label = new Integer(i + 1).toString();

			if (course.isValidControl(i)) {
				g.strokeText(label, xPos[i + 1]
						- (g.measureText(label).getWidth() / 2), y);
			}
		}

		// Annotate the last label
		int i = course.getNumControls() - 1;
		String s = new Integer(i + 1).toString() + " "
				+ Message.get("Graph.Finish");

		g.strokeText(s, xPos[i + 1], y);

		// Draw "Start" label
		String start = Message.get("Graph.Start");

		g.strokeText(start, xPos[0] - (g.measureText(start).getWidth() / 2), y);

		// ...and the time at the bottom of the graph
		y = yAxis.getMaxPixel() + this.FONT_HEIGHT;

		for (i = 0; i < xAxis.getNumLabels(); i++) {
			g.strokeText(xAxis.getLabelString(i), xAxis.getLabelPixel(i), y);
		}

		i = xAxis.getNumLabels() - 1;
		g.strokeText(Message.get("Graph.Time"), xAxis.getLabelPixel(i)
				- (g.measureText("Graph.Time").getWidth() / 2), y
				+ this.FONT_HEIGHT);
		g.strokeText(eventName, xAxis.getLabelPixel(0), y + this.FONT_HEIGHT);

		// time on the left axis
		String labelSuffix = "";

		if (graphType == GraphType.PERCENTBEHIND) {
			labelSuffix = " %";
		}

		for (i = 0; i < yAxis.getNumLabels(); i++) {
			s = yAxis.getLabelString(i) + labelSuffix;

			int x = (int) (xAxis.getMinPixel() - g.measureText(s).getWidth());

			g.strokeText(s, x, yAxis.getLabelPixel(i) + halfFontHeight);
		}

		// Paint selected split
		if (selectedSplit >= -1) {
			int w = 1;

			g.setStrokeStyle(this.textColor);

			// gridColor);
			// Draw line at split
			g.fillRect(xPos[selectedSplit + 1] - w, yAxis.getMinPixel(), w,
					yAxis.getMaxPixel() - yAxis.getMinPixel());
		}
	}

	private Result getDisplayedResult(int i) {
		int resultNo = ((Integer) displayedResults.elementAt(i)).intValue();

		return selectedResults.getResult(resultNo);
	}

	private void drawResults(Context2d g) {

		legendSlotHeight = this.FONT_HEIGHT - 4;

		int halfFontHeight = this.FONT_HEIGHT / 2;

		/*
		 * Draw the results
		 */
		reference = selectedResults.getReferenceResult();

		Integer[] sortedByFinishTime = new Integer[displayedResults.size()];
		int[] legend = new int[displayedResults.size()];
		int nToSort = 0;

		for (int i = 0; i < displayedResults.size(); i++) {
			/*
			 * Set the line style
			 */
			g.setStrokeStyle(displayColors.elementAt(i).value());

			Result result = getDisplayedResult(i);
			Time t;
			Time tOld = getYFirst(result);
			int yOld = yAxis.toPixel(tOld.asSeconds());
			int xOld = xPos[0];

			for (int splitCount = 0; splitCount < (result.getNumControls() + 1); splitCount++) {
				t = getYTime(result, splitCount);

				int y = yAxis.toPixel(t.asSeconds());

				// Only draw the line if both results are valid
				if (t.isValid() && tOld.isValid()) {
					// Draw three lines to make a thick one
					drawLine(g, xOld, yOld, xPos[splitCount + 1], y);

					if (thickLine) {
						drawLine(g, xOld, yOld + 1, xPos[splitCount + 1], y + 1);
						drawLine(g, xOld + 1, yOld, xPos[splitCount + 1] + 1, y);
					}
				}

				yOld = y;
				xOld = xPos[splitCount + 1];
				tOld = t;
			}

			// Add text on the right of the graph
			Time yTime = getYTime(result, course.getNumControls());

			if (!yTime.isValid()) {
				yTime = Time.MAXTIME;
			}

			result.setSortTime(yTime);
			sortedByFinishTime[nToSort++] = new Integer(i + 1);

			// Add Text to left of graph
			if (graphType == GraphType.ABSOLUTE) {
				int y;

				if (getYFirst(result).isValid()) {
					y = yAxis.toPixel(getYFirst(result).asSeconds());
					g.setStrokeStyle(this.textColor);
				} else {
					y = yAxis.getMaxPixel() - this.FONT_HEIGHT;
					g.setStrokeStyle(CssColor.make("Red"));
				}

				g.strokeText(getLeftText(result), LEFT_MARGIN, y);
			}
		}
		
		Sorter.Sort(sortedByFinishTime, 0, nToSort - 1, lt);
	

		int maxYLegend = yAxis.getMaxPixel() + legendSlotHeight;
		int maxShownRunners = ((maxYLegend - yAxis.getMinPixel()) / legendSlotHeight) - 1;

		if (nToSort > maxShownRunners) {
			nToSort = maxShownRunners;
		}

		int oldY = -1000;

		for (int i = 0; i < nToSort; i++) {
			Result result = getDisplayedResult(sortedByFinishTime[i].intValue() - 1);
			int y = yAxis.toPixel(result.getSortTime().asSeconds())
					+ halfFontHeight;

			if ((y - oldY) < legendSlotHeight) {
				y = oldY + legendSlotHeight;
			}

			legend[i] = y;

			if (y > maxYLegend) {
				int moveDist = y - maxYLegend;
				int i1 = i;

				while ((i1 >= 0) && (moveDist >= 0)) {
					legend[i1] -= moveDist;

					if (i1 > 0) {
						moveDist = legendSlotHeight
								- (legend[i1] - legend[i1 - 1]);
					}

					i1 -= 1;
				}
			}

			oldY = legend[i];
		}

		int xright = xAxis.getMaxPixel();

		for (int posL = 0; posL < nToSort; posL++) {
			if (legend[posL] != 0) {
				int y = legend[posL];

				g.setStrokeStyle(displayColors
						.elementAt(sortedByFinishTime[posL].intValue() - 1));
				drawLine(g, xright + 4, y - halfFontHeight, xright + 16, y
						- halfFontHeight);
				drawLine(g, xright + 4, y - halfFontHeight + 1, xright + 16, y
						- halfFontHeight + 1);
				g.setStrokeStyle(this.textColor);
				g.strokeText(
						getRightText(
								getDisplayedResult(sortedByFinishTime[posL]
										.intValue() - 1), selectedSplit),
						xright + 20, y);
			}
		}

		// Display error of start times are not available in race graph
		if (graphType == GraphType.ABSOLUTE) {
			if (selectedResults.getResult(0) != null) {
				Result r = selectedResults.getResult(0);

				if (!r.getStartTime().isValid()) {
					g.setStrokeStyle(CssColor.make("red"));

					g.setFont("15px SansSerif");

					int xpos = xAxis.getMinPixel() + 15;
					g.strokeText(Message.get("Graph.NoRaceGraph1"), xpos, 50);
					g.strokeText(Message.get("Graph.NoRaceGraph2"), xpos, 70);
				}
			}
		}

	}

	void initHandlers() {
		canvas.addMouseMoveHandler(new MouseMoveHandler() {
			public void onMouseMove(MouseMoveEvent event) {
				int mouseX = event.getRelativeX(canvas.getElement());

				updateSelectedIndex(mouseX);
			}
		});

		canvas.addMouseOutHandler(new MouseOutHandler() {
			public void onMouseOut(MouseOutEvent event) {

			}
		});

		canvas.addTouchMoveHandler(new TouchMoveHandler() {
			public void onTouchMove(TouchMoveEvent event) {
				event.preventDefault();
				if (event.getTouches().length() > 0) {
					Touch touch = event.getTouches().get(0);
					int mouseX = touch.getRelativeX(canvas.getElement());

					updateSelectedIndex(mouseX);
				}
				event.preventDefault();
			}
		});

		canvas.addTouchEndHandler(new TouchEndHandler() {
			public void onTouchEnd(TouchEndEvent event) {
				event.preventDefault();
			}
		});

		canvas.addGestureStartHandler(new GestureStartHandler() {
			public void onGestureStart(GestureStartEvent event) {
				event.preventDefault();
			}
		});
	}

	private boolean updateSelectedIndex(int mouseX) {
		int newSelectedSplit = -2;

		if (course != null) {
			// System.out.println("MouseMoved");

			newSelectedSplit = selectedSplit;

			for (int xIndex = 0; xIndex <= (course.getNumControls() + 1); xIndex++) {
				if (mouseX < ((xPos[xIndex] + xPos[xIndex + 1]) / 2)) {
					// Note that the x positions are offset 1 from the split
					// indices
					newSelectedSplit = xIndex - 1;

					break;
				}
			}
		} else {
			newSelectedSplit = -2;
		}

		if (newSelectedSplit != selectedSplit) {
			selectedSplit = newSelectedSplit;

			return true;
		}

		return false;
	}
	
	   private class LessThanResult implements ICompare {
	        public boolean lessThan(Object a, Object b) {
	            return getDisplayedResult(((Integer) a).intValue() - 1).getSortTime()
	                       .lessThan(getDisplayedResult(((Integer) b).intValue() -
	                                                    1).getSortTime());
	        }
	    }
}

